package com.leyou.search.service;

import com.leyou.item.Bo.SpuBo;
import com.leyou.item.pojo.Brand;
import com.leyou.item.pojo.Category;
import com.leyou.item.pojo.SpecParam;
import com.leyou.item.pojo.Spu;
import com.leyou.search.Repository.GoodsRepository;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import sun.util.PreHashedMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Log4j2
@Service
public class SearchService {
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;
    @Autowired
    private SpecClient specClient;

    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private IndexService indexService;

    @Autowired
    private GoodsRepository goodsRepository;

    public SearchResult search(SearchRequest request) {
        int page = request.getPage() - 1;//elastic默认从o页开始的
        int size = request.getSize();
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {//用户未输入查询条件
            return null;
        }
        //根据elastic的api查询;
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //基本查询
        //调用方法，构造布尔查询条件；
        //MatchQueryBuilder query = QueryBuilders.matchQuery("all", key);//条件查询
        //添加过滤字段，根据过滤字段进行聚合
        QueryBuilder query = buildBasicQueryWithFilter(request);
        queryBuilder.withQuery(query);
        //过滤查询，保留需要的字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));

        //根据id分别做聚合:聚合之后得到聚合对象遍历得到桶,分别根据id求出商品,和分类列表
        String brandAggName = "brand";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        String categoryAggName = "category";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //添加排序条件
        if (StringUtils.isNotBlank(request.getSortBy())) {
            queryBuilder.withSort(SortBuilders.fieldSort(request.getSortBy()).order(request.getDescending() ? SortOrder.DESC : SortOrder.ASC));
        }

        //设置查询条件;分页查询
        queryBuilder.withPageable(PageRequest.of(page, size));
        //获取查询结果,分页,由于有聚合,要用模板接收
        AggregatedPage<Goods> result = this.elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //获取聚合之后的结果,多个聚合对象,
        Aggregations aggregations = result.getAggregations();
        //分别获取商品和分类的聚合
        LongTerms brandTerms = aggregations.get(brandAggName);
        LongTerms categoryTerms = aggregations.get(categoryAggName);

        //调用方法，根据聚合的结果，获取品牌和分类的数据
        List<Brand> brands = getBrands(brandTerms);
        List<Category> categories = getCategory(categoryTerms);


        //根据分类的id聚合规格参数，，判断分类的长度，如果分类的长度为1则进行聚合
        List<Map<String, Object>> specs = null;
        if (categories.size() == 1) {
            specs = getSpesc(categories.get(0).getId(), query);
        }


        long total = result.getTotalElements();
        long totalPage = (total + size - 1) / size;

        return new SearchResult(total, totalPage, result.getContent(), categories, brands, specs);
    }

    //=============================================================================================
    //构建基本查询条件,过滤查询
    private QueryBuilder buildBasicQueryWithFilter(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //基本查询条件,
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //过滤条件过滤器
        BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
        //整理过滤条件
        Map<String, String> filter = request.getFilter();
        //遍历过滤项，得到key，继续聚合
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            // 商品分类和品牌已经聚合;
            if (key != "cid3" && key != "brandId") {
                key = "specs." + key + ".keyword";
            }
            filterQueryBuilder.must(QueryBuilders.termQuery(key, value));
        }
        //把过滤查询加入到基本查询中
        queryBuilder.filter(filterQueryBuilder);
        return queryBuilder;
    }

    //====================================================================
    //满足分类的长度为1，聚合分类规格参数
    private List<Map<String, Object>> getSpesc(Long cid, QueryBuilder query) {
        List<Map<String, Object>> specs = new ArrayList<>();//保存spec规格参数
        //调用服务。根据cid查询规格参数列表,
        List<SpecParam> specParams = this.specClient.querySpecParam(null, cid, true, null);
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(query);//条件查询
        //遍历规格参数，对规格参数的名字进行聚合
        specParams.forEach(specParam -> {
            String name = specParam.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name)
                    .field("specs." + name + ".keyword"));
        });
        //获取规格参数的结果.解析
        AggregatedPage pages = (AggregatedPage) this.goodsRepository.search(queryBuilder.build());
        //遍历规格参数，对规格参数的名字进行聚合
        for (SpecParam specParam : specParams) {
            Map<String, Object> map = new HashMap<>();
            String name = specParam.getName();
            map.put("k", name);//添加每个规格参数的k就是规格参数的名字
            //获取指定名称的聚合结果
            StringTerms terms = (StringTerms) pages.getAggregation(name);
            //获取聚合之后的桶,遍历并得到每个规格参数对应的值
            List<String> strings = terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList());
            map.put("options", strings);
            specs.add(map);//封装到spec中；
        }
        return specs;
    }

    //========================================================================================
    //根据分类cid3聚合分类
    private List<Category> getCategory(LongTerms categoryTerms) {
        try {
            //遍历获取ids集合,根据ids查询分类的信息,把查询分类名字和id组合返回
            List<Category> categories = new ArrayList<>();
            List<Long> cids = categoryTerms.getBuckets().stream()
                    .map(c -> c.getKeyAsNumber().longValue()).collect(Collectors.toList());
            //根据ids查询分类的信息
            List<String> names = this.categoryClient.queryNameByIds(cids);
            //把查询分类名字和id组合返回
            for (int i = 0; i < names.size(); i++) {
                Category category = new Category();
                category.setName(names.get(i));
                category.setId(cids.get(i));//添加name和id
                categories.add(category);
            }
            return categories;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("分类聚合异常" + e);
            return null;

        }
    }

    //=========================================================================
    //根据品牌的id聚合品牌
    private List<Brand> getBrands(LongTerms brandTerms) {
        try {
            //遍历获取ids集合,根据ids查询商品的信息
            List<Long> bids = brandTerms.getBuckets().stream().map(
                    b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Brand> brands = this.brandClient.queryBrandByIds(bids);
            return brands;
        } catch (Exception e) {
            log.error("品牌聚合异常" + e);
            e.printStackTrace();
            return null;
        }
    }


    //拓展方法新增和修改商品,根据商品id
    public void createIndex(Long id) {
        //根据商品id查询spu,再调用方法获取goods,新增goods
        Spu spu = this.goodsClient.querySpuById(id);
        SpuBo spuBo = new SpuBo();
        BeanUtils.copyProperties(spu, spuBo);
        Goods goods = this.indexService.buildGoods(spuBo);
        this.goodsRepository.save(goods);//保存变更的商品
    }
    public void deleteIndex(Long id){
        //根据id删除goods
        this.goodsRepository.deleteById(id);
    }

}
